/**
 * author: ethermedia
 *
 * 这不是一个万能的 可序列化Map，只包含简单的必要功能
 *
 * @class SerializableMap
 * @extends CCObject
 */

import {
    _decorator,
    CCObject,
    Component,
    Eventify,
    Node,
    ValueType,
} from 'cc';
const { ccclass, property } = _decorator;
type Primitive = string | number;

@ccclass('SerializableMap')
export class SerializableMap<K extends Primitive, V extends ValueType>
    extends CCObject
    implements Map<K, V>
{
    @property
    private _keys: K[] = Array<K>();

    @property
    private _values: V[] = Array<V>();

    private _kIndex: Map<K, number> = new Map();
    private _allocSize: number = 100;

    constructor(...args: any) {
        super(...args);
    }

    destroy(): boolean {
        this._keys.length = 0;
        this._values.length = 0;
        this._kIndex.clear();
        return super.destroy();
    }

    static __deserialize__(
        deserializer: any,
        object: Record<string, unknown>,
        deserialized: Record<string, unknown>,
        constructor: any,
    ): void {
        object._keys = deserialized._keys;
        object._values = deserialized._values;
        object._name = deserialized._name;
        object._objFlags = deserialized._objFlags;

        //Engine doesn't has a callback when a CCObject is deserialized. You have to inject to it's deserialization process.
        //Or, you need to initialized the index map by hand.
        if (object.onDeserialized instanceof Function) {
            (object as any).onDeserialized();
        }
    }

    onDeserialized(): void {
        this.initIndex();
    }

    private initIndex() {
        this.pack();

        for (let i = 0; i < this._keys.length; i++) {
            if (this._keys[i] == null) {
                continue;
            }

            this._kIndex.set(this._keys[i], i);
        }
    }

    private pack() {
        let firstEmtpyIndex = -1;
        let findingEmptyKey = true;
        for (
            let scanningIndex = 0;
            scanningIndex < this._keys.length;
            scanningIndex++
        ) {
            if (findingEmptyKey && this._keys[scanningIndex] == null) {
                firstEmtpyIndex = scanningIndex;
                findingEmptyKey = false;
                continue;
            }

            if (!findingEmptyKey && this._keys[scanningIndex] != null) {
                this._keys[firstEmtpyIndex] = this._keys[scanningIndex];
                this._values[firstEmtpyIndex] =
                    this._values[scanningIndex];

                this._keys[scanningIndex] = null;
                this._values[scanningIndex] = null;
                scanningIndex = firstEmtpyIndex + 1;
                findingEmptyKey = true;
                continue;
            }
        }
    }

    getIndex(key: K): number {
        if (!this._kIndex.has(key)) {
            return -1;
        }
        return this._kIndex.get(key);
    }

    //-----Map interfaces-----

    clear(): void {
        this._keys.length = 0;
        this._values.length = 0;
        this._kIndex.clear();
    }

    delete(key: K): boolean {
        let index = this.getIndex(key);
        if (index == -1) {
            return;
        }
        // this._keys.splice(index, 1);
        // this._values.splice(index, 1);
        this._keys[index] = null;
        this._values[index] = null;
        this._kIndex.delete(key);
    }

    forEach(
        callbackfn: (value: V, key: K, map: Map<K, V>) => void,
        thisArg?: any,
    ): void {
        for (let i = 0; i < this._keys.length; i++) {
            callbackfn(this._values[i], this._keys[i], this);
        }
    }

    get(key: K): V | undefined {
        let index = this.getIndex(key);
        if (index == -1) {
            return undefined;
        }
        return this._values[index];
    }

    has(key: K): boolean {
        let index = this.getIndex(key);
        if (index == -1) {
            return false;
        }
        return true;
    }

    private _nextIndex: number = 0;
    set(key: K, value: V): this {
        if (key == null) {
            console.warn(
                'SerializableMap.set(key), key can not be null or undefined.',
            );
            return undefined;
        }
        let index = this.getIndex(key);
        if (index == -1) {
            //allocate a chunk for decreasing too many sizing operations.
            if (this._keys.length <= this._nextIndex) {
                this._keys.length += this._allocSize;
                this._values.length += this._allocSize;
            }
            this._keys[this._nextIndex] = key;
            this._values[this._nextIndex] = value;
            this._kIndex.set(key, this._nextIndex);
            this._nextIndex++;
            return this;
        } else {
            this._values[index] = value;
            return this;
        }
    }

    get size(): number {
        return this._kIndex.size;
    }

    keys(): IterableIterator<K> {
        return this._keys.values();
    }

    values(): IterableIterator<V> {
        return this._values.values();
    }

    [Symbol.toStringTag] = 'SerializableMap';

    entries(): IterableIterator<[K, V]> {
        let me = this;
        let keys = this.keys();
        return (function* () {
            for (let key of keys) {
                yield [key, me.get(key)];
            }
        })();
    }

    [Symbol.iterator](): IterableIterator<[K, V]> {
        return this.entries();
    }
}
